using System;
using System.Net.Sockets;
using System.Text;

namespace Easy521WPF
{
    /// <summary>
    /// Modbus TCP 客户端通讯类
    /// 用于与汇川Easy521 PLC进行Modbus TCP通讯
    /// </summary>
    public class ModbusTcpClient
    {
        private TcpClient tcpClient;
        private NetworkStream networkStream;
        private ushort transactionId = 0x1234; // 事务标识符，每次发送时递增

        /// <summary>
        /// 连接状态
        /// </summary>
        public bool IsConnected => tcpClient != null && tcpClient.Connected;

        /// <summary>
        /// 连接到PLC服务器
        /// </summary>
        /// <param name="ipAddress">PLC的IP地址</param>
        /// <param name="port">端口号，默认502</param>
        /// <param name="timeout">超时时间(毫秒)</param>
        public void Connect(string ipAddress, int port, int timeout)
        {
            try
            {
                tcpClient = new TcpClient();
                tcpClient.ReceiveTimeout = timeout;
                tcpClient.SendTimeout = timeout;
                tcpClient.Connect(ipAddress, port);
                networkStream = tcpClient.GetStream();
            }
            catch (Exception ex)
            {
                throw new Exception($"连接失败: {ex.Message}");
            }
        }

        /// <summary>
        /// 断开连接
        /// </summary>
        public void Disconnect()
        {
            try
            {
                if (networkStream != null)
                {
                    networkStream.Close();
                    networkStream = null;
                }
                if (tcpClient != null)
                {
                    tcpClient.Close();
                    tcpClient = null;
                }
            }
            catch { }
        }

        /// <summary>
        /// 获取下一个事务ID
        /// </summary>
        private ushort GetNextTransactionId()
        {
            transactionId++;
            if (transactionId == 0) transactionId = 1; // 避免使用0
            return transactionId;
        }

        /// <summary>
        /// 构建Modbus TCP帧
        /// </summary>
        /// <param name="unitId">单元标识符(通常为1)</param>
        /// <param name="functionCode">功能码</param>
        /// <param name="data">数据部分</param>
        /// <returns>完整的Modbus TCP帧</returns>
        private byte[] BuildModbusTcpFrame(byte unitId, byte functionCode, byte[] data)
        {
            ushort transId = GetNextTransactionId();
            ushort protocolId = 0x0000; // Modbus协议标识符
            ushort length = (ushort)(data.Length + 2); // 单元ID + 功能码 + 数据长度

            byte[] frame = new byte[6 + 1 + 1 + data.Length];
            int index = 0;

            // MBAP头部(7字节)
            frame[index++] = (byte)(transId >> 8);      // 事务标识符高字节
            frame[index++] = (byte)(transId & 0xFF);    // 事务标识符低字节
            frame[index++] = (byte)(protocolId >> 8);   // 协议标识符高字节
            frame[index++] = (byte)(protocolId & 0xFF); // 协议标识符低字节
            frame[index++] = (byte)(length >> 8);       // 长度高字节
            frame[index++] = (byte)(length & 0xFF);     // 长度低字节
            frame[index++] = unitId;                    // 单元标识符

            // PDU部分
            frame[index++] = functionCode;              // 功能码
            Array.Copy(data, 0, frame, index, data.Length); // 数据

            return frame;
        }

        /// <summary>
        /// 发送Modbus TCP命令并接收响应
        /// </summary>
        /// <param name="unitId">单元标识符</param>
        /// <param name="functionCode">功能码</param>
        /// <param name="data">数据</param>
        /// <returns>响应数据</returns>
        public byte[] SendCommand(byte unitId, byte functionCode, byte[] data)
        {
            if (!IsConnected)
                throw new Exception("未连接到服务器");

            try
            {
                // 构建并发送命令
                byte[] command = BuildModbusTcpFrame(unitId, functionCode, data);
                networkStream.Write(command, 0, command.Length);

                // 接收响应
                byte[] response = new byte[1024];
                int bytesRead = networkStream.Read(response, 0, response.Length);

                if (bytesRead > 0)
                {
                    byte[] result = new byte[bytesRead];
                    Array.Copy(response, result, bytesRead);
                    return result;
                }
                else
                {
                    throw new Exception("接收超时");
                }
            }
            catch (Exception ex)
            {
                throw new Exception($"通讯错误: {ex.Message}");
            }
        }

        /// <summary>
        /// 读取线圈状态 (功能码01)
        /// 用于读取Y输出状态
        /// </summary>
        /// <param name="startAddress">起始地址</param>
        /// <param name="count">读取数量</param>
        /// <returns>线圈状态数组</returns>
        public bool[] ReadCoils(ushort startAddress, ushort count)
        {
            byte[] data = new byte[4];
            data[0] = (byte)(startAddress >> 8);
            data[1] = (byte)(startAddress & 0xFF);
            data[2] = (byte)(count >> 8);
            data[3] = (byte)(count & 0xFF);

            byte[] response = SendCommand(0x01, 0x01, data);

            if (response != null && response.Length >= 9)
            {
                int byteCount = response[8];
                bool[] coils = new bool[count];

                for (int i = 0; i < count && i < byteCount * 8; i++)
                {
                    int byteIndex = i / 8;
                    int bitIndex = i % 8;
                    coils[i] = (response[9 + byteIndex] & (1 << bitIndex)) != 0;
                }

                return coils;
            }

            return null;
        }

        /// <summary>
        /// 读取离散量输入 (功能码02)
        /// 用于读取X输入状态
        /// </summary>
        /// <param name="startAddress">起始地址</param>
        /// <param name="count">读取数量</param>
        /// <returns>离散量输入状态数组</returns>
        public bool[] ReadDiscreteInputs(ushort startAddress, ushort count)
        {
            byte[] data = new byte[4];
            data[0] = (byte)(startAddress >> 8);
            data[1] = (byte)(startAddress & 0xFF);
            data[2] = (byte)(count >> 8);
            data[3] = (byte)(count & 0xFF);

            byte[] response = SendCommand(0x01, 0x02, data);

            if (response != null && response.Length >= 9)
            {
                int byteCount = response[8];
                bool[] inputs = new bool[count];

                for (int i = 0; i < count && i < byteCount * 8; i++)
                {
                    int byteIndex = i / 8;
                    int bitIndex = i % 8;
                    inputs[i] = (response[9 + byteIndex] & (1 << bitIndex)) != 0;
                }

                return inputs;
            }

            return null;
        }

        /// <summary>
        /// 读取保持寄存器 (功能码03)
        /// 用于读取D寄存器数据
        /// </summary>
        /// <param name="startAddress">起始地址</param>
        /// <param name="count">寄存器数量</param>
        /// <returns>寄存器值数组</returns>
        public ushort[] ReadHoldingRegisters(ushort startAddress, ushort count)
        {
            byte[] data = new byte[4];
            data[0] = (byte)(startAddress >> 8);
            data[1] = (byte)(startAddress & 0xFF);
            data[2] = (byte)(count >> 8);
            data[3] = (byte)(count & 0xFF);

            byte[] response = SendCommand(0x01, 0x03, data);

            if (response != null && response.Length >= 9)
            {
                int byteCount = response[8];
                ushort[] registers = new ushort[count];

                for (int i = 0; i < count && (i * 2 + 1) < byteCount; i++)
                {
                    registers[i] = (ushort)((response[9 + i * 2] << 8) | response[9 + i * 2 + 1]);
                }

                return registers;
            }

            return null;
        }

        /// <summary>
        /// 写单个线圈 (功能码05)
        /// 用于写单个Y输出
        /// </summary>
        /// <param name="address">线圈地址</param>
        /// <param name="value">状态值(true为开启，false为关闭)</param>
        public void WriteSingleCoil(ushort address, bool value)
        {
            byte[] data = new byte[4];
            data[0] = (byte)(address >> 8);
            data[1] = (byte)(address & 0xFF);
            data[2] = (byte)(value ? 0xFF : 0x00);
            data[3] = 0x00;

            SendCommand(0x01, 0x05, data);
        }

        /// <summary>
        /// 写单个寄存器 (功能码06)
        /// 用于写单个D寄存器
        /// </summary>
        /// <param name="address">寄存器地址</param>
        /// <param name="value">寄存器值</param>
        public void WriteSingleRegister(ushort address, ushort value)
        {
            byte[] data = new byte[4];
            data[0] = (byte)(address >> 8);
            data[1] = (byte)(address & 0xFF);
            data[2] = (byte)(value >> 8);
            data[3] = (byte)(value & 0xFF);

            SendCommand(0x01, 0x06, data);
        }

        /// <summary>
        /// 写多个线圈 (功能码0F)
        /// 用于写多个Y输出
        /// </summary>
        /// <param name="startAddress">起始地址</param>
        /// <param name="values">线圈状态数组</param>
        public void WriteMultipleCoils(ushort startAddress, bool[] values)
        {
            int byteCount = (values.Length + 7) / 8;
            byte[] data = new byte[5 + byteCount];

            data[0] = (byte)(startAddress >> 8);
            data[1] = (byte)(startAddress & 0xFF);
            data[2] = (byte)(values.Length >> 8);
            data[3] = (byte)(values.Length & 0xFF);
            data[4] = (byte)byteCount;

            for (int i = 0; i < values.Length; i++)
            {
                if (values[i])
                {
                    int byteIndex = i / 8;
                    int bitIndex = i % 8;
                    data[5 + byteIndex] |= (byte)(1 << bitIndex);
                }
            }

            SendCommand(0x01, 0x0F, data);
        }

        /// <summary>
        /// 写多个寄存器 (功能码10)
        /// 用于写多个D寄存器
        /// </summary>
        /// <param name="startAddress">起始地址</param>
        /// <param name="values">寄存器值数组</param>
        public void WriteMultipleRegisters(ushort startAddress, ushort[] values)
        {
            byte[] data = new byte[5 + values.Length * 2];

            data[0] = (byte)(startAddress >> 8);
            data[1] = (byte)(startAddress & 0xFF);
            data[2] = (byte)(values.Length >> 8);
            data[3] = (byte)(values.Length & 0xFF);
            data[4] = (byte)(values.Length * 2);

            for (int i = 0; i < values.Length; i++)
            {
                data[5 + i * 2] = (byte)(values[i] >> 8);
                data[5 + i * 2 + 1] = (byte)(values[i] & 0xFF);
            }

            SendCommand(0x01, 0x10, data);
        }

        /// <summary>
        /// 将字节数组转换为十六进制字符串(用于日志)
        /// </summary>
        public static string BytesToHex(byte[] bytes)
        {
            if (bytes == null) return "";
            StringBuilder sb = new StringBuilder();
            foreach (byte b in bytes)
            {
                sb.Append(b.ToString("X2") + " ");
            }
            return sb.ToString().TrimEnd();
        }
    }
}
